﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;

namespace ZyGame.Execute
{
    public interface IRunning : IDisposable
    {
        string name { get; set; }
    }

    public enum RunState : byte
    {
        None,
        Running,
        Completed,
        Canceled,
    }

    public class Running : IDisposable
    {
        private Coroutine enumerator;
        private Func<IEnumerator> func;
        public RunState state { get; private set; } = RunState.None;

        public Running(Func<IEnumerator> func)
        {
            this.func = func;
            state = RunState.None;
        }

        public void Cancel()
        {
            if (state != RunState.Completed && state != RunState.Canceled)
            {
                return;
            }

            if (enumerator is null)
            {
                return;
            }

            state = RunState.Canceled;
            Client.Tools.StopCoroutine(enumerator);
        }

        public bool TryCancel()
        {
            Cancel();
            return state == RunState.Canceled;
        }

        public IEnumerator Runnable()
        {
            if (state != RunState.None)
            {
                yield break;
            }

            state = RunState.Running;
            enumerator = Client.Tools.StartCoroutine(func());
            yield return enumerator;
            state = RunState.Completed;
        }

        public void Dispose()
        {
            enumerator = null;
            func = null;
        }
    }

    public class Runnable : Runnable<object>, IRunning
    {
        public Runnable()
        {
        }
    }

    public class Runnable<T> : IRunning
    {
        private Exception exception;
        private Coroutine enumerator;
        private Queue<Running> taskList = new Queue<Running>();

        public T result { get; protected set; }
        public RunState state { get; internal set; } = RunState.None;
        public string name { get; set; }

        public Runnable()
        {
            Client.Tools.Wait(0.01f, () => { Client.Tools.StartCoroutine(Waitting()); });
        }

        public Runnable<T> SetResult(T result)
        {
            this.result = result;
            return this;
        }

        public Runnable<T> SetResult(Exception exception)
        {
            this.exception = exception;
            return this;
        }

        public virtual bool EnsureExecutedSuccessful()
        {
            if (exception == null)
            {
                return true;
            }

            Client.Console.WriteError(exception);
            return false;
        }

        public virtual void Dispose()
        {
            taskList.Clear();
            result = default;
            exception = null;
            if (enumerator != null)
            {
                Client.Tools.StopCoroutine(enumerator);
            }

            enumerator = null;
            GC.SuppressFinalize(this);
        }

        public virtual IEnumerator Waitting()
        {
            if (state == RunState.None)
            {
                state = RunState.Running;
                enumerator = Client.Tools.StartCoroutine(DoRunning());
            }

            yield return new WaitUntil(CheckCompleted);
        }

        protected virtual bool CheckCompleted()
        {
            return state == RunState.Completed || state == RunState.Canceled;
        }

        protected virtual IEnumerator DoRunning()
        {
            while (state == RunState.Running && taskList.TryDequeue(out Running task))
            {
                yield return task.Runnable();
                task.Dispose();
            }

            state = RunState.Completed;
            enumerator = null;
            Client.Tools.Wait(0.1f, RemoveThisRunnable);
        }

        private void RemoveThisRunnable()
        {
            RunnableManager.instance.Remove(name);
        }

        public virtual Runnable<T> Execute(Func<IEnumerator> func)
        {
            taskList.Enqueue(new Running(func));
            return this;
        }
    }
}